package vg_connector.implementation;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import vg_connector.interfaces.AnswerAdapter;
import vg_connector.interfaces.IClientAPIService;
import vg_connector.interfaces.IClientListener;
import vg_connector.interfaces.actions.ParentAction;
import vg_connector.interfaces.actions.ParentAnswer;
import vg_connector.interfaces.actions.ParentMessage;
import vg_connector.interfaces.actions.PingAction;
import vg_connector.interfaces.exceptions.ConnectException;

import java.util.List;
import java.util.Map;
import java.util.UUID;

/**
 * @author Timur Zolotuhin (tzolotuhin@parallels.com)
 */
public class ClientAPIService implements IClientAPIService {
    // Main data
    private List<IClientListener> clientListeners = Lists.newArrayList();
    private ChannelWorker channelWorker;
    private UUID serverId;
    private ChannelListener innerChannelListener;
    private Map<UUID, AnswerAdapter> answerListeners;

    // Mutex
    private final Object generalMutex = new Object();

    public ClientAPIService() {
        serverId = new UUID(0, 0);

        innerChannelListener = new ChannelListener() {
            @Override
            public void connect(UUID id, int type) {
                if (type == PingAction.SERVER_OWNER_ROLE) {
                    synchronized (generalMutex) {
                        serverId = id;
                    }
                    doNotifyConnectListeners(syncCopyClientListeners());
                }
            }

            @Override
            public void disconnect(UUID id) {
                synchronized (generalMutex) {
                    if (serverId.equals(id))
                        serverId = new UUID(0, 0);
                }
                doNotifyDisconnectListeners(syncCopyClientListeners());
            }

            @Override
            public void receive(ParentMessage message) {
                if (message instanceof ParentAnswer) {
                    ParentAnswer parentAnswer = (ParentAnswer)message;
                    AnswerAdapter answerListener = null;
                    synchronized (generalMutex) {
                        if (answerListeners.containsKey(parentAnswer.getActionId()))
                            answerListener = answerListeners.remove(parentAnswer.getActionId());
                    }
                    if (answerListener != null)
                        answerListener.receive(parentAnswer);
                }
            }
        };
    }

    @Override
    public void connect(UUID instanceId) throws ConnectException {
        synchronized (generalMutex) {
            try {
                if (channelWorker != null && channelWorker.isConnect())
                    disconnect();

                channelWorker = new ChannelWorker(instanceId, PingAction.CLIENT_OWNER_ROLE);
                channelWorker.addChannelListener(innerChannelListener);
                channelWorker.connect();

                answerListeners = Maps.newHashMap();
            } catch (Throwable ex) {
                throw new ConnectException(ex);
            }
        }
    }

    @Override
    public void disconnect() throws ConnectException {
        synchronized (generalMutex) {
            channelWorker.disconnect();
            channelWorker.removeChannelListener(innerChannelListener);
        }
    }

    @Override
    public void sendAction(ParentAction action, AnswerAdapter answerListener) throws ConnectException {
        ChannelWorker tmpChannelWorker;
        synchronized (generalMutex) {
            if (serverId == null)
                throw new ConnectException("Unknown server id");

            action.setToId(serverId);
            answerListeners.put(action.getActionId(), answerListener);
            tmpChannelWorker = channelWorker;
        }

        try {
            tmpChannelWorker.sendMessage(action);
        } catch (Throwable ex) {
            throw new ConnectException(ex);
        }
    }

    @Override
    public void addClientListener(IClientListener clientListener) {
        synchronized (generalMutex) {
            clientListeners.add(clientListener);
        }
    }

//==============================================================================
//------------------PRIVATE METHODS---------------------------------------------
    private List<IClientListener> syncCopyClientListeners() {
        List<IClientListener> copyListeners;

        synchronized (generalMutex) {
            copyListeners = Lists.newArrayList(clientListeners);
        }

        return copyListeners;
    }

    private void doNotifyConnectListeners(List<IClientListener> clientListeners) {
        for (IClientListener clientListener : clientListeners) {
            clientListener.connect();
        }
    }

    private void doNotifyDisconnectListeners(List<IClientListener> clientListeners) {
        for (IClientListener clientListener : clientListeners) {
            clientListener.disconnect();
        }
    }
}
